
#pragma once

#include <vector>
#include <shlobj.h>

using namespace std;

class CEnumFormatEtc : public IEnumFORMATETC
{
public:
    CEnumFormatEtc(const vector < FORMATETC >& vFormatEtc)
    {
        m_nRefCount = 0;
        m_nIndex = 0;
        m_vFormatEtc = vFormatEtc;
    }

protected:
    vector < FORMATETC > m_vFormatEtc;
    int m_nRefCount;
    int m_nIndex;

public:
    // IUnknown members
    STDMETHOD(QueryInterface)(REFIID refiid, void FAR* FAR* ppvObject)
    {
        *ppvObject = (refiid == IID_IUnknown || refiid == IID_IEnumFORMATETC) ? this : NULL;

        if (*ppvObject != NULL)
            ((LPUNKNOWN)*ppvObject)->AddRef();

        return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
    }

    STDMETHOD_(ULONG, AddRef)(void)
    {
        return ++m_nRefCount;
    }

    STDMETHOD_(ULONG, Release)(void)
    {
        int nRefCount = --m_nRefCount;

        if (nRefCount == 0)
            delete this;

        return nRefCount;
    }

    // IEnumFORMATETC members
    STDMETHOD(Next)(ULONG celt, LPFORMATETC lpFormatEtc, ULONG FAR *pceltFetched)
    {
        if (pceltFetched != NULL)
            *pceltFetched = 0;

        ULONG cReturn = celt;

        if (celt <= 0 || lpFormatEtc == NULL || m_nIndex >= (int)m_vFormatEtc.size())
            return S_FALSE;

        if (pceltFetched == NULL && celt != 1)   // pceltFetched can be NULL only for 1 item request
            return S_FALSE;

        while (m_nIndex < (int)m_vFormatEtc.size() && cReturn > 0) {
            *lpFormatEtc++ = m_vFormatEtc[ m_nIndex++ ];
            cReturn--;
        }

        if (pceltFetched != NULL)
            *pceltFetched = celt - cReturn;

        return cReturn == 0 ? S_OK : S_FALSE;
    }

    STDMETHOD(Skip)(ULONG celt)
    {
        if ((m_nIndex + (int)celt) >= (int)m_vFormatEtc.size())
            return S_FALSE;

        m_nIndex += celt;
        return S_OK;
    }

    STDMETHOD(Reset)(void)
    {
        m_nIndex = 0;
        return S_OK;
    }

    STDMETHOD(Clone)(IEnumFORMATETC FAR * FAR* ppCloneEnumFormatEtc)
    {
        if (ppCloneEnumFormatEtc == NULL)
            return E_POINTER;

        *ppCloneEnumFormatEtc = new CEnumFormatEtc(m_vFormatEtc);
        ((CEnumFormatEtc*)*ppCloneEnumFormatEtc)->AddRef();
        ((CEnumFormatEtc*)*ppCloneEnumFormatEtc)->m_nIndex = m_nIndex;
        return S_OK;
    }
};

class CDropSource : public IDropSource
{
public:
    CDropSource()
    {
        m_nRefCount = 0;
    }

protected:
    int m_nRefCount;

public:
    // IUnknown members
    STDMETHOD(QueryInterface)(REFIID refiid, void FAR* FAR* ppvObject)
    {
        *ppvObject = (refiid == IID_IUnknown || refiid == IID_IDropSource) ? this : NULL;

        if (*ppvObject != NULL)
            ((LPUNKNOWN)*ppvObject)->AddRef();

        return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
    }

    STDMETHOD_(ULONG, AddRef)(void)
    {
        return ++m_nRefCount;
    }

    STDMETHOD_(ULONG, Release)(void)
    {
        int nRefCount = --m_nRefCount;

        if (nRefCount == 0)
            delete this;

        return nRefCount;
    }

    // IDropSource members
    STDMETHOD(QueryContinueDrag)(BOOL bEscapePressed, DWORD dwKeyState)
    {
        if (bEscapePressed)
            return DRAGDROP_S_CANCEL;

        if (!(dwKeyState & (MK_LBUTTON | MK_RBUTTON)))
            return DRAGDROP_S_DROP;

        return S_OK;
    }

    STDMETHOD(GiveFeedback)(DWORD dwEffect)
    {
        return DRAGDROP_S_USEDEFAULTCURSORS;
    }
};

class CDataObject : public IDataObject
{
public:
    CDataObject(CDropSource *pDropSource)
    {
        m_nRefCount = 0;
        m_pDropSource = pDropSource;
        m_bSwappedButtons = GetSystemMetrics(SM_SWAPBUTTON);
    }

    virtual ~CDataObject()
    {
        for (vector < STGMEDIUM >::iterator posStgMedium = m_vStgMedium.begin(); posStgMedium != m_vStgMedium.end(); posStgMedium++)
            ReleaseStgMedium(&(*posStgMedium));
    }

protected:
    CDropSource *m_pDropSource;
    int m_nRefCount;
    BOOL m_bSwappedButtons;

    vector < FORMATETC > m_vFormatEtc;
    vector < STGMEDIUM > m_vStgMedium;

public:
    // IUnknown members
    STDMETHOD(QueryInterface)(REFIID refiid, void FAR* FAR* ppvObject)
    {
        *ppvObject = (refiid == IID_IUnknown || refiid == IID_IDataObject) ? this : NULL;

        if (*ppvObject != NULL)
            ((LPUNKNOWN)*ppvObject)->AddRef();

        return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
    }

    STDMETHOD_(ULONG, AddRef)(void)
    {
        return ++m_nRefCount;
    }

    STDMETHOD_(ULONG, Release)(void)
    {
        int nRefCount = --m_nRefCount;

        if (nRefCount == 0)
            delete this;

        return nRefCount;
    }

    // IDataObject members
    STDMETHOD(GetData)(FORMATETC __RPC_FAR *pformatetcIn, STGMEDIUM __RPC_FAR *pmedium)
    {
        if (pformatetcIn == NULL || pmedium == NULL)
            return E_INVALIDARG;

        ZeroMemory(pmedium, sizeof(STGMEDIUM));

        for (int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++) {
            if (pformatetcIn->tymed & m_vFormatEtc[ nFormatEtc ].tymed &&
                pformatetcIn->dwAspect == m_vFormatEtc[ nFormatEtc ].dwAspect &&
                pformatetcIn->cfFormat == m_vFormatEtc[ nFormatEtc ].cfFormat) {
                if (m_vStgMedium[ nFormatEtc ].tymed == TYMED_NULL)
                    return OnRenderData(m_vFormatEtc[ nFormatEtc ], pmedium, (GetAsyncKeyState(m_bSwappedButtons ? VK_RBUTTON : VK_LBUTTON) >= 0)) ? S_OK : DV_E_FORMATETC;

                CopyMedium(pmedium, m_vStgMedium[ nFormatEtc ], m_vFormatEtc[ nFormatEtc ]);
                return S_OK;
            }
        }

        return DV_E_FORMATETC;
    }

    STDMETHOD(GetDataHere)(FORMATETC __RPC_FAR *pformatetc, STGMEDIUM __RPC_FAR *pmedium)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(QueryGetData)(FORMATETC __RPC_FAR *pformatetc)
    {
        if (pformatetc == NULL)
            return E_INVALIDARG;

        if (!(pformatetc->dwAspect & DVASPECT_CONTENT))
            return DV_E_DVASPECT;

        HRESULT hResult = DV_E_TYMED;

        for (int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++) {
            if (!(pformatetc->tymed & m_vFormatEtc[ nFormatEtc ].tymed)) {
                hResult = DV_E_TYMED;
                continue;
            }

            if (pformatetc->cfFormat == m_vFormatEtc[ nFormatEtc ].cfFormat)
                return S_OK;

            hResult = DV_E_CLIPFORMAT;
        }

        return hResult;
    }

    STDMETHOD(GetCanonicalFormatEtc)(FORMATETC __RPC_FAR *pformatectIn, FORMATETC __RPC_FAR *pformatetcOut)
    {
        return pformatetcOut == NULL ? E_INVALIDARG : DATA_S_SAMEFORMATETC;
    }

    STDMETHOD(SetData)(FORMATETC __RPC_FAR *pformatetc, STGMEDIUM __RPC_FAR *pmedium, BOOL bRelease)
    {
        if (pformatetc == NULL || pmedium == NULL)
            return E_INVALIDARG;

        m_vFormatEtc.push_back(*pformatetc);
        STGMEDIUM StgMedium = *pmedium;

        if (!bRelease)
            CopyMedium(&StgMedium, *pmedium, *pformatetc);

        m_vStgMedium.push_back(StgMedium);
        return S_OK;
    }

    STDMETHOD(EnumFormatEtc)(DWORD dwDirection, IEnumFORMATETC __RPC_FAR *__RPC_FAR *ppenumFormatEtc)
    {
        if (ppenumFormatEtc == NULL)
            return E_POINTER;

        switch (dwDirection) {
        case DATADIR_GET:
            *ppenumFormatEtc = new CEnumFormatEtc(m_vFormatEtc);
            ((CEnumFormatEtc*)*ppenumFormatEtc)->AddRef();
            return S_OK;

        default:
            *ppenumFormatEtc = NULL;
            return E_NOTIMPL;
        }
    }

    STDMETHOD(DAdvise)(FORMATETC __RPC_FAR *pformatetc, DWORD advf, IAdviseSink __RPC_FAR *pAdvSink, DWORD __RPC_FAR *pdwConnection)
    {
        return OLE_E_ADVISENOTSUPPORTED;
    }

    STDMETHOD(DUnadvise)(DWORD dwConnection)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(EnumDAdvise)(IEnumSTATDATA __RPC_FAR *__RPC_FAR *ppenumAdvise)
    {
        return OLE_E_ADVISENOTSUPPORTED;
    }

    void CopyMedium(STGMEDIUM *pMedDest, STGMEDIUM& MedSrc, FORMATETC& FmtSrc)
    {
        switch (MedSrc.tymed) {
        case TYMED_HGLOBAL:
            pMedDest->hGlobal = (HGLOBAL)OleDuplicateData(MedSrc.hGlobal, FmtSrc.cfFormat, NULL);
            break;

        case TYMED_GDI:
            pMedDest->hBitmap = (HBITMAP)OleDuplicateData(MedSrc.hBitmap, FmtSrc.cfFormat, NULL);
            break;

        case TYMED_MFPICT:
            pMedDest->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(MedSrc.hMetaFilePict, FmtSrc.cfFormat, NULL);
            break;

        case TYMED_ENHMF:
            pMedDest->hEnhMetaFile = (HENHMETAFILE)OleDuplicateData(MedSrc.hEnhMetaFile, FmtSrc.cfFormat, NULL);
            break;

        case TYMED_FILE:
            pMedDest->lpszFileName = (LPOLESTR)OleDuplicateData(MedSrc.lpszFileName, FmtSrc.cfFormat, NULL);
            break;

        case TYMED_ISTREAM:
            pMedDest->pstm = MedSrc.pstm;
            MedSrc.pstm->AddRef();
            break;

        case TYMED_ISTORAGE:
            pMedDest->pstg = MedSrc.pstg;
            MedSrc.pstg->AddRef();
            break;
        }

        pMedDest->tymed = MedSrc.tymed;
        pMedDest->pUnkForRelease = NULL;

        if (MedSrc.pUnkForRelease != NULL) {
            pMedDest->pUnkForRelease = MedSrc.pUnkForRelease;
            MedSrc.pUnkForRelease->AddRef();
        }
    }

    virtual BOOL OnRenderData(FORMATETC& FormatEtc, STGMEDIUM *pStgMedium, BOOL bDropComplete)
    {
        return FALSE;
    }
};

class CDropTarget : public IDropTarget
{
public:
    CDropTarget(HWND hTargetWnd)
    {
        m_hTargetWnd = hTargetWnd;
        m_nRefCount = 0;
        m_bAllowDrop = FALSE;
        m_pDropTargetHelper = NULL;
        ZeroMemory(&m_FormatEtc, sizeof(FORMATETC));
        ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM));

        if (FAILED(CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, (LPVOID*)&m_pDropTargetHelper)))
            m_pDropTargetHelper = NULL;
    }

    virtual ~CDropTarget()
    {
        if (m_pDropTargetHelper != NULL) {
            m_pDropTargetHelper->Release();
            m_pDropTargetHelper = NULL;
        }
    }

protected:
    HWND m_hTargetWnd;
    int m_nRefCount;
    struct IDropTargetHelper *m_pDropTargetHelper;
    vector < FORMATETC > m_vFormatEtc;
    BOOL m_bAllowDrop;
    FORMATETC m_FormatEtc;
    STGMEDIUM m_StgMedium;

public:
    // IUnknown members
    STDMETHOD(QueryInterface)(REFIID refiid, void FAR* FAR* ppvObject)
    {
        *ppvObject = (refiid == IID_IUnknown || refiid == IID_IDropTarget) ? this : NULL;

        if (*ppvObject != NULL)
            ((LPUNKNOWN)*ppvObject)->AddRef();

        return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
    }

    STDMETHOD_(ULONG, AddRef)(void)
    {
        return ++m_nRefCount;
    }

    STDMETHOD_(ULONG, Release)(void)
    {
        int nRefCount = --m_nRefCount;

        if (nRefCount == 0)
            delete this;

        return nRefCount;
    }

    STDMETHOD(DragEnter)(IDataObject __RPC_FAR *pDataObject, DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect)
    {
        if (pDataObject == NULL)
            return E_INVALIDARG;

        if (m_pDropTargetHelper != NULL)
            m_pDropTargetHelper->DragEnter(m_hTargetWnd, pDataObject, (LPPOINT)&pt, *pdwEffect);

        ZeroMemory(&m_FormatEtc, sizeof(FORMATETC));

        if (m_StgMedium.tymed != TYMED_NULL)
            ReleaseStgMedium(&m_StgMedium);

        ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM));

        for (int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++) {
            STGMEDIUM StgMedium;
            m_bAllowDrop = (pDataObject->GetData(&m_vFormatEtc[ nFormatEtc ], &StgMedium) == S_OK);

            if (m_bAllowDrop) {
                // store drag data for later use in DragOver
                m_FormatEtc = m_vFormatEtc[ nFormatEtc ];
                m_StgMedium = StgMedium;
                // get client cursor position
                CWindow hWnd(m_hTargetWnd);
                CPoint point(pt.x, pt.y);
                hWnd.ScreenToClient(&point);
                *pdwEffect = OnDragEnter(m_FormatEtc, m_StgMedium, dwKeyState, point);
                break;
            }
        }

        QueryDrop(dwKeyState, pdwEffect);
        return S_OK;
    }

    STDMETHOD(DragOver)(DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect)
    {
        if (m_pDropTargetHelper)
            m_pDropTargetHelper->DragOver((LPPOINT)&pt, *pdwEffect);

        if (m_bAllowDrop && m_FormatEtc.cfFormat != CF_NULL && m_StgMedium.tymed != TYMED_NULL) {
            // get client cursor position
            CWindow hWnd(m_hTargetWnd);
            CPoint point(pt.x, pt.y);
            hWnd.ScreenToClient(&point);
            *pdwEffect = OnDragOver(m_FormatEtc, m_StgMedium, dwKeyState, point);
        }

        QueryDrop(dwKeyState, pdwEffect);
        return S_OK;
    }

    STDMETHOD(DragLeave)(void)
    {
        if (m_pDropTargetHelper)
            m_pDropTargetHelper->DragLeave();

        OnDragLeave();
        m_bAllowDrop = FALSE;
        ZeroMemory(&m_FormatEtc, sizeof(FORMATETC));

        if (m_StgMedium.tymed != TYMED_NULL)
            ReleaseStgMedium(&m_StgMedium);

        ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM));
        return S_OK;
    }

    STDMETHOD(Drop)(IDataObject __RPC_FAR *pDataObject, DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect)
    {
        if (pDataObject == NULL)
            return E_INVALIDARG;

        if (m_pDropTargetHelper)
            m_pDropTargetHelper->Drop(pDataObject, (LPPOINT)&pt, *pdwEffect);

        if (m_bAllowDrop && m_FormatEtc.cfFormat != CF_NULL && QueryDrop(dwKeyState, pdwEffect)) {
            STGMEDIUM StgMedium;

            if (pDataObject->GetData(&m_FormatEtc, &StgMedium) == S_OK) {
                // get client cursor position
                CWindow hWnd(m_hTargetWnd);
                CPoint point(pt.x, pt.y);
                hWnd.ScreenToClient(&point);

                if (!OnDrop(m_FormatEtc, StgMedium, *pdwEffect, point))
                    *pdwEffect = DROPEFFECT_NONE;

                ReleaseStgMedium(&StgMedium);
            }
        }

        m_bAllowDrop = FALSE;
        ZeroMemory(&m_FormatEtc, sizeof(FORMATETC));

        if (m_StgMedium.tymed != TYMED_NULL)
            ReleaseStgMedium(&m_StgMedium);

        ZeroMemory(&m_StgMedium, sizeof(STGMEDIUM));
        return S_OK;
    }

    void AddSupportedFormat(FORMATETC& FormatEtc)
    {
        m_vFormatEtc.push_back(FormatEtc);
    }

    void AddSupportedFormat(CLIPFORMAT cfFormat)
    {
        FORMATETC FormatEtc;
        ZeroMemory(&FormatEtc, sizeof(FORMATETC));
        FormatEtc.cfFormat = cfFormat;
        FormatEtc.dwAspect = DVASPECT_CONTENT;
        FormatEtc.lindex = -1;
        FormatEtc.tymed = TYMED_HGLOBAL;
        AddSupportedFormat(FormatEtc);
    }

    BOOL QueryDrop(DWORD dwKeyState, LPDWORD pdwEffect)
    {
        DWORD dwEffects = *pdwEffect;

        if (!m_bAllowDrop) {
            *pdwEffect = DROPEFFECT_NONE;
            return FALSE;
        }

        *pdwEffect = (dwKeyState & MK_CONTROL) ? ((dwKeyState & MK_SHIFT) ? DROPEFFECT_LINK : DROPEFFECT_COPY) : ((dwKeyState & MK_SHIFT) ? DROPEFFECT_MOVE : 0);

        if (*pdwEffect == 0) {
            if (dwEffects & DROPEFFECT_COPY)
                *pdwEffect = DROPEFFECT_COPY;
            else if (dwEffects & DROPEFFECT_MOVE)
                *pdwEffect = DROPEFFECT_MOVE;
            else if (dwEffects & DROPEFFECT_LINK)
                *pdwEffect = DROPEFFECT_LINK;
            else
                *pdwEffect = DROPEFFECT_NONE;
        } else if (!(*pdwEffect & dwEffects))
            *pdwEffect = DROPEFFECT_NONE;

        return (*pdwEffect != DROPEFFECT_NONE);
    }

    virtual DWORD OnDragEnter(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point)
    {
        return FALSE;
    }

    virtual DWORD OnDragOver(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point)
    {
        return FALSE;
    }

    virtual BOOL OnDrop(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwEffect, CPoint point)
    {
        return FALSE;
    }

    virtual void OnDragLeave()
    {
    }
};

template < class T >
class CDropTargetT : public CDropTarget
{
public:
    CDropTargetT(HWND hTargetWnd) : CDropTarget(hTargetWnd)
    {
        m_pDelegate = NULL;
    }

protected:
    T *m_pDelegate;

public:
    BOOL Register(T *pDelegate)
    {
        m_pDelegate = pDelegate;
        return TRUE;
    }

    virtual DWORD OnDragEnter(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point)
    {
        return m_pDelegate == NULL ? DROPEFFECT_NONE : m_pDelegate->OnDragEnter(FormatEtc, StgMedium, dwKeyState, point);
    }

    virtual DWORD OnDragOver(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point)
    {
        return m_pDelegate == NULL ? DROPEFFECT_NONE : m_pDelegate->OnDragOver(FormatEtc, StgMedium, dwKeyState, point);
    }

    virtual BOOL OnDrop(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwEffect, CPoint point)
    {
        return m_pDelegate == NULL ? FALSE : m_pDelegate->OnDrop(FormatEtc, StgMedium, dwEffect, point);
    }

    virtual void OnDragLeave()
    {
        if (m_pDelegate != NULL)
            m_pDelegate->OnDragLeave();
    }
};

template < class T >
class CDataObjectT : public CDataObject
{
public:
    CDataObjectT(CDropSource *pDropSource) : CDataObject(pDropSource)
    {
        m_pDelegate = FALSE;
    }

protected:
    T *m_pDelegate;

public:
    BOOL Register(T *pDelegate)
    {
        m_pDelegate = pDelegate;
        return TRUE;
    }

    virtual BOOL OnRenderData(FORMATETC& FormatEtc, STGMEDIUM *pStgMedium, BOOL bDropComplete)
    {
        return m_pDelegate == NULL ? FALSE : m_pDelegate->OnRenderData(FormatEtc, pStgMedium, bDropComplete);
    }
};

template < class T >
class CDragDrop
{
public:
    CDragDrop()
    {
        m_pDropSource = NULL;
        m_pDataObject = NULL;
        m_pDropTarget = NULL;
        m_hTargetWnd = NULL;
    }

    virtual ~CDragDrop()
    {
        if (m_pDropSource != NULL)
            m_pDropSource->Release();

        if (m_pDataObject != NULL)
            m_pDataObject->Release();
    }

protected:
    CDropSource *m_pDropSource;
    CDataObjectT< T > *m_pDataObject;
    CDropTargetT< T > *m_pDropTarget;
    HWND m_hTargetWnd;

public:
    BOOL Register(T *pDelegate, BOOL bDropSource = TRUE)
    {
        m_hTargetWnd = pDelegate->m_hWnd;
        // instantiate new drop target object
        m_pDropTarget = new CDropTargetT< T >(m_hTargetWnd);
        m_pDropTarget->Register(pDelegate);

        // register drop target
        if (FAILED(RegisterDragDrop(m_hTargetWnd, m_pDropTarget))) {
            m_pDropTarget = NULL;
            return FALSE;
        }

        // is this a drop target only?
        if (!bDropSource)
            return TRUE;

        // instantiate new drop source object
        m_pDropSource = new CDropSource;
        m_pDropSource->AddRef();
        m_pDataObject = new CDataObjectT< T >(m_pDropSource);
        m_pDataObject->AddRef();
        // register drop source delegate for data render
        return m_pDataObject->Register(pDelegate);
    }

    BOOL Revoke()
    {
        m_pDropTarget = NULL;
        return (RevokeDragDrop(m_hTargetWnd) == S_OK);
    }

    BOOL AddTargetFormat(CLIPFORMAT cfFormat)
    {
        if (m_pDropTarget == NULL)
            return FALSE;

        m_pDropTarget->AddSupportedFormat(cfFormat);
        return TRUE;
    }

    BOOL AddSourceFormat(CLIPFORMAT cfFormat)
    {
        if (m_pDataObject == NULL)
            return FALSE;

        FORMATETC FormatEtc;
        ZeroMemory(&FormatEtc, sizeof(FORMATETC));
        FormatEtc.cfFormat = cfFormat;
        FormatEtc.dwAspect = DVASPECT_CONTENT;
        FormatEtc.lindex = -1;
        FormatEtc.tymed = TYMED_HGLOBAL;
        STGMEDIUM StgMedium;
        ZeroMemory(&StgMedium, sizeof(STGMEDIUM));
        return SUCCEEDED(m_pDataObject->SetData(&FormatEtc, &StgMedium, TRUE));
    }

    BOOL SetClipboard(FORMATETC& FormatEtc, STGMEDIUM& StgMedium)
    {
        if (m_pDataObject == NULL)
            return DROPEFFECT_NONE;

        if (FAILED(m_pDataObject->SetData(&FormatEtc, &StgMedium, TRUE)))
            return DROPEFFECT_NONE;

        return (OleSetClipboard(m_pDataObject) == S_OK);
    }

    BOOL FlushClipboard()
    {
        return (OleFlushClipboard() == S_OK);
    }

    DWORD DoDragDrop(SHDRAGIMAGE *pDragImage = NULL, DWORD dwValidEffects = DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK)
    {
        if (m_pDataObject == NULL)
            return DROPEFFECT_NONE;

        IDragSourceHelper *pDragSourceHelper = NULL;

        // instantiate drag source helper object
        if (pDragImage != NULL) {
            if (FAILED(CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, IID_IDragSourceHelper, (LPVOID*)&pDragSourceHelper)))
                pDragSourceHelper = NULL;

            if (pDragSourceHelper != NULL)
                pDragSourceHelper->InitializeFromBitmap(pDragImage, m_pDataObject);
        }

        DWORD dwEffects = DROPEFFECT_NONE;
        dwEffects = ::DoDragDrop(m_pDataObject, m_pDropSource, dwValidEffects, &dwEffects) == DRAGDROP_S_DROP ? DROPEFFECT_NONE : dwEffects;

        // destroy drag source helper object
        if (pDragSourceHelper != NULL)
            pDragSourceHelper->Release();

        return dwEffects;
    }
};
